﻿
/**
 * nPnP - neither Perl not PHP
 *
 * Copyright (C) 2009 Christian Moeller
 *
 * This program is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU General Public License as published by the 
 * Free Software Foundation; either version 3 of the License, or (at your option) 
 * any later version.
 * 
 * This program is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; 
 * if not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FParser.Parser.Expressions;

namespace FParser.Parser
{
    public class TokenParser
    {
        /// <summary>
        /// Modifiers for methods
        /// </summary>
        private string[] Modifiers = new string[] {
            "public", "private", "static", "global" };

        /// <summary>
        /// List of tokens parsed by Token Parser
        /// </summary>
        private List<Token> tokens;

        /// <summary>
        /// Creates a new instance of TokenParser
        /// </summary>
        /// <param name="tokens"></param>
        public TokenParser(List<Token> tokens)
        {
            this.tokens = tokens;
        }

        /// <summary>
        /// Parse the List
        /// </summary>
        /// <returns></returns>
        public List<Expression> Parse()
        {
            tokenPos = 0;
            List<Expression> expressions = new List<Expression>();

            Expression exp;

            while ((exp = _parse()) != null)
            {
                // We do not need the semicolon, that makes the tree much smaller
                if (exp.type != EnumExpressionType.SEMICOLON)
                    expressions.Add(exp);
            }

            return expressions;
        }

        private int gModifiers;

        private Expression _parse()
        {
            Expression begin = new Expression();
            begin.type = EnumExpressionType.BEGIN;

            gModifiers = 0;

            Expression expression;

            while ((expression = parseNext()).type != EnumExpressionType.END)
            {
                switch (expression.type)
                {
                    #region END
                    case EnumExpressionType.END:
                        return expression;
                    #endregion

                    #region TYPE
                    case EnumExpressionType.TYPE:
                        Expression rightHand = parseNext();
                        if (rightHand.type == EnumExpressionType.ASSIGNMENT ||
                            rightHand.type == EnumExpressionType.OPERATION)
                        {
                            expression = parseAssignment(expression);

                            return expression;
                        }
                        else if (rightHand.type == EnumExpressionType.BIND)
                        {
                            Expression method = parseNext();
                            if (method.type != EnumExpressionType.STATIC_DATA)
                            {
                                Console.WriteLine("Error, expecting method identifiert after bind statement!");
                                Environment.Exit(-1);
                            }
                            if (previewNext().tokenType != EnumTokenType.SEMICOLON)
                            {
                                Console.WriteLine("Error, expecting semicolon after method bind!");
                                Environment.Exit(-1);
                            }
                            BindExpression bex = new BindExpression();
                            bex.Type = (TypeExpression) expression;
                            bex.MethodDelegate = method.data;

                            return bex;
                        }
                        else
                        {
                            rewindToken();
                            expression.type = EnumExpressionType.TYPE;
                            return expression;
                        }

                    #endregion

                    #region CALL
                    case EnumExpressionType.CALL:
                        if (typeof(CallExpression) != expression.GetType())
                        {
                            expression = parseCall(expression);

                            if (previewExpression().type == EnumExpressionType.OPERATION)
                            {
                                forwardToken();
                                Expression nExp = parseAssignment(expression);
                                return nExp;
                            }
                        }

                        return expression;
                    #endregion

                    #region STATIC DATA
                    case EnumExpressionType.STATIC_DATA:
                        Expression nextExpr = parseNext();

                        if (nextExpr != null && (
                            nextExpr.type == EnumExpressionType.ASSIGNMENT ||
                            nextExpr.type == EnumExpressionType.OPERATION))
                        {
                            expression = parseAssignment(expression);
                        }
                        else
                        {
                            rewindToken();
                        }

                        return expression;
                    #endregion

                    #region COMMA, BRACKET, SEMICOLON
                    case EnumExpressionType.COMMA:
                    case EnumExpressionType.BRACKET:
                    case EnumExpressionType.SEMICOLON:
                    case EnumExpressionType.M_CLOSE:
                    case EnumExpressionType.M_OPEN:
                        return expression;
                    #endregion

                    #region MODIFIERS
                    case EnumExpressionType.MODIFIER:
                        switch (expression.data)
                        {
                            case "public":
                                gModifiers &= (int)EnumModifiers.PUBLIC;
                                break;
                            case "private":
                                gModifiers &= (int)EnumModifiers.PRIVATE;
                                break;
                            case "global":
                                gModifiers &= (int)EnumModifiers.GLOBAL;
                                break;
                        }
                        break;
                    #endregion

                    #region METHOD
                    case EnumExpressionType.METHOD:
                        return parseMethod();
                        break;
                    #endregion

                    #region RETURN
                    case EnumExpressionType.RETURN:
                        Expression type = _parse();
                        ReturnExpression ret = new ReturnExpression();
                        ret.left = type;
                        return ret;

                    #endregion

                    #region IF
                    case EnumExpressionType.IF:
                        expression = parseIf();
                        return expression;
                        break;
                    #endregion

                    #region WHILE
                    case EnumExpressionType.WHILE:
                        expression = parseWhile();
                        return expression;
                        break;
                    #endregion
                }
            }

            return null;
        }

        private Expression parseWhile()
        {
            WhileExpression whileExp = new WhileExpression();
            requireNext(EnumExpressionType.BRACKET);

            do
            {
                Expression exp = parseNext();
                if (exp.type != EnumExpressionType.BRACKET)
                {
                    Expression bExp = parseBoolean(exp);
                    Expression bCat = parseNext();

                    whileExp.BoolExp.Add((BooleanExpression)bExp);

                    if (bCat.type != EnumExpressionType.BOOLEAN)
                        break;
                    else
                    {
                        switch (bCat.data)
                        {
                            case "and":
                                whileExp.OpList.Add(EnumBooleanOperator.BOOL_AND);
                                break;
                            case "or":
                                whileExp.OpList.Add(EnumBooleanOperator.BOOL_OR);
                                break;
                        }
                        continue;
                    }
                }
                else
                    break;
            } while (true);

            requireNext(EnumExpressionType.M_OPEN);

            do
            {
                Expression sub = _parse();

                if (sub == null)
                {
                    Console.WriteLine("Expecting } at end of if statement.");
                    Environment.Exit(0);
                }

                if (sub.type != EnumExpressionType.M_CLOSE)
                    whileExp.Content.Add(sub);
                else
                    break;

            } while (true);

            return whileExp;
        }

        private Expression parseIf()
        {
            IfExpression ifExp = new IfExpression();

            requireNext(EnumExpressionType.BRACKET);
            
            do
            {
                Expression exp = parseNext();

                //if (exp.type == EnumExpressionType.TYPE)
             

                if (exp.type != EnumExpressionType.BRACKET)
                {
                    Expression bExp = parseBoolean(exp);
                    Expression bCat = parseNext();

                    ifExp.Expressions.Add((BooleanExpression)bExp);

                    if (bCat.type != EnumExpressionType.BOOLEAN)
                        break;
                    else
                    {
                        switch (bCat.data)
                        {
                            case "and":
                                ifExp.OpList.Add(EnumBooleanOperator.BOOL_AND);
                                break;
                            case "or":
                                ifExp.OpList.Add(EnumBooleanOperator.BOOL_OR);
                                break;
                        }
                        continue;
                    }
                }
                else
                    break;
            } while (true);

            requireNext(EnumExpressionType.M_OPEN);
            
            do
            {
                Expression sub = _parse();

                if (sub == null)
                {
                    Console.WriteLine("Expecting } at end of if statement.");
                    Environment.Exit(0);
                }

                if (sub.type != EnumExpressionType.M_CLOSE)
                    ifExp.Content.Add(sub);
                else
                    break;

            } while (true);

            if (previewExpression().type == EnumExpressionType.ELSE)
            {
                forwardToken();

                requireNext(EnumExpressionType.M_OPEN);

                ifExp.HasElse = true;

                do
                {
                    Expression sub = _parse();

                    if (sub == null)
                    {
                        Console.WriteLine("Expecting } at end of else.");
                        Environment.Exit(0);
                    }

                    if (sub.type != EnumExpressionType.M_CLOSE)
                        ifExp.ElseContent.Add(sub);
                    else
                        break;

                } while (true);
            }


            return ifExp;
        }

        private Expression parseBoolean(Expression _left)
        {
            Expression left = _left;
            Expression bop = requireNext(EnumExpressionType.BOOLEAN);
            Expression right = _parse();

            BooleanExpression bexp = new BooleanExpression();
            bexp.left = left;
            bexp.right = right;

            switch (bop.data)
            {
                case "==":
                    bexp.BoolOperator = EnumBooleanOperator.EQUAL;
                    break;
                case "!=":
                    bexp.BoolOperator = EnumBooleanOperator.NOTEQUAL;
                    break;
                case ">":
                    bexp.BoolOperator = EnumBooleanOperator.GREATER;
                    break;
                case "<":
                    bexp.BoolOperator = EnumBooleanOperator.SMALLER;
                    break;
                case ">=":
                    bexp.BoolOperator = EnumBooleanOperator.GREATER_EQUAL;
                    break;
                case "<=":
                    bexp.BoolOperator = EnumBooleanOperator.SMALLER_EQUAL;
                    break;
                default:
                    Console.WriteLine("Operator expected");
                    Environment.Exit(-1);
                    break;
            }

            return bexp;
        }

        private Expression parseMethod()
        {
            MethodExpression method = new MethodExpression();
            Expression name = requireNext(EnumExpressionType.STATIC_DATA);

            //if (name.type != EnumExpressionType.STATIC_DATA)
            method.MethodName = name.data;

            requireNext(EnumExpressionType.M_OPEN);

            do
            {
                Expression sub = _parse();

                if (sub == null)
                {
                    Console.WriteLine("Expecting } at end of method.");
                    Environment.Exit(0);
                }

                if (sub.type != EnumExpressionType.M_CLOSE)
                    method.Content.Add(sub);
                else
                    break;

            } while (true);

            return method;
        }

        private Expression parseAssignment(Expression leftType)
        {
            rewindToken();
            Expression left = leftType;
            Expression _operator = parseNext();
            Expression right = _parse();

            if (_operator.type == EnumExpressionType.ASSIGNMENT)
            {
                AssignmentExpression assignment = new AssignmentExpression(left, right);
                return assignment;
            }
            else if (_operator.type == EnumExpressionType.OPERATION)
            {
                EnumOperatorType op = (EnumOperatorType)(int)_operator.data[0];
                OperationExpression assignment = new OperationExpression(left, op, right);

                return assignment;
            }
            else
            {
                Console.WriteLine("Expecting operator to proceed assignment.");
                Environment.Exit(-1);
            }

            return null;

          }

        private Expression parseCall(Expression _left)
        {
            Expression left = _left;
            CallExpression call;
            if (left.type == EnumExpressionType.TYPE)
            {
                call = new CallExpression((TypeExpression)left);
            }
            else
            {
                call = new CallExpression(left.data);
            }
                
           // CallExpression call = new CallExpression(left.data);

            requireNext(EnumExpressionType.BRACKET); // bracket open

            do
            {
                Expression right = _parse();

                if (right != null &&
                    right.token.tokenType != EnumTokenType.BRACKER_CLOSE)
                    call.AddParameter(right);
                else
                    break;
     
                Expression follow = parseNext();
                if (follow.type == EnumExpressionType.COMMA)
                    continue;
                else if (follow.token.tokenType == EnumTokenType.BRACKER_CLOSE)
                    break;

            } while (true);

 

            return call;
        }

        int tokenPos;

        private Expression parseNext()
        {
            Expression exp = new Expression();
            exp.data = "";
          
            Token token = nextToken();

            if (token.tokenType == EnumTokenType.END)
                return Expression.END;

            exp.token = token;
            switch (token.tokenType)
            {
                case EnumTokenType.DOLLAR:
                    Token typeName = nextToken();
                    if (typeName.tokenType == EnumTokenType.DATA)
                    {
                        TypeExpression texp = new TypeExpression();
                        texp.data = typeName.data;
                        do
                        {
                            Token isDot = previewNext();
                            if (isDot.tokenType == EnumTokenType.DOT)
                            {
                                forwardToken();
                                Token _token;
                                if ((_token = previewNext()).tokenType == EnumTokenType.DATA)
                                {
                                    texp.DepthList.Add(_token.data);
                                    forwardToken();
                                }
                            }
                            else if (isDot.tokenType == EnumTokenType.BRACKET_OPEN)
                            {
                                //rewindToken();
                                Expression callExp = parseCall(texp);
                                return callExp;
                            }
                            else if (isDot.tokenType == EnumTokenType.SBRACKET_OPEN)
                            {
                                forwardToken();
                                Expression arrayValue = _parse();
                                texp.ArrayIndex = arrayValue;
                            }
                            else
                            {
                                break;
                            }
                        } while (true);

                        return texp;
                    }
                    else
                    {
                        Console.WriteLine("Expected type name after $");
                        Environment.Exit(-1);
                    }
                    break;

                case EnumTokenType.EQUAL:
                    Token rightHand = nextToken();
                    
                    rewindToken();

                    if (rightHand.tokenType == EnumTokenType.EQUAL) {
                        exp.type = EnumExpressionType.BOOLEAN;
                        exp.data = "==";
                        return exp;    
                    }

                    exp.type = EnumExpressionType.ASSIGNMENT;
                    exp.data = ((char)EnumTokenType.EQUAL).ToString();
                    return exp;

                    break;

                case EnumTokenType.COMMA:
                    exp.type = EnumExpressionType.COMMA;
                    return exp;
                    break;

                case EnumTokenType.DQUOTES:
                    exp.type = EnumExpressionType.STATIC_DATA;
                    exp.data = token.data;
                    return exp;
                    break;

                case EnumTokenType.BRACKET_OPEN:
                case EnumTokenType.BRACKER_CLOSE:
                    exp.type = EnumExpressionType.BRACKET;
                    return exp;

                case EnumTokenType.BRACE_OPEN:
                    exp.type = EnumExpressionType.M_OPEN;
                    return exp;
                case EnumTokenType.BRACE_CLOSE:
                    exp.type = EnumExpressionType.M_CLOSE;
                    return exp;

                case EnumTokenType.MINUS:
                    Token prevNext = previewNext();
                   /* if (prevNext.tokenType == EnumTokenType.DATA &&
                        IsNumber(prevNext.data))
                    {
                        exp.type = EnumExpressionType.STATIC_DATA;
                        exp.data = int.Parse("-" + prevNext.data).ToString();
                        return exp;
                    }
                    else*/
                    {
                        exp.type = EnumExpressionType.OPERATION;
                        exp.data = ((char)token.tokenType).ToString();
                        return exp;
                    }
                    break;

                case EnumTokenType.PLUS:
                case EnumTokenType.MULTI:
                case EnumTokenType.DIV:
                case EnumTokenType.AND:
                case EnumTokenType.OR:
                    exp.type = EnumExpressionType.OPERATION;
                    exp.data = ((char)token.tokenType).ToString();
                    return exp;
                    break;

                case EnumTokenType.NOT:
                    if (previewNext().tokenType == EnumTokenType.EQUAL)
                    {
                        exp.type = EnumExpressionType.BOOLEAN;
                        exp.data = "!=";
                        return exp;
                    }
                    else
                    {
                        exp.type = EnumExpressionType.OPERATION;
                        exp.data = ((char)token.tokenType).ToString();
                        return exp;
                    }
                   

                case EnumTokenType.DATA:

                 /*   if (token.data == "use")
                    {
                        exp.type = EnumExpressionType.USE;
                        return exp;
                    }*/

                    if (token.data == "bind")
                    {
                        exp.type = EnumExpressionType.BIND;
                        return exp;
                    }

                    if (token.data == "while")
                    {
                        exp.type = EnumExpressionType.WHILE;
                        return exp;
                    }

                    if (token.data == "<" || token.data == ">")
                    {
                        Token _nextToken = nextToken();
                        rewindToken();
                        if (_nextToken.tokenType == EnumTokenType.EQUAL)
                            token.data += "=";

                        exp.type = EnumExpressionType.BOOLEAN;
                        exp.data = token.data;
                        return exp;
                    }

                    if (token.data == "and" || token.data == "or")
                    {
                        exp.type = EnumExpressionType.BOOLEAN;
                        exp.data = token.data;
                        return exp;
                    }

                    if (token.data == "if")
                    {
                        exp.type = EnumExpressionType.IF;
                        return exp;
                    }

                    if (token.data == "else")
                    {
                        exp.type = EnumExpressionType.ELSE;
                        return exp;
                    }


                    if (token.data == "return")
                    {
                        exp.type = EnumExpressionType.RETURN;
                        return exp;
                    }

                    if (token.data == "function")
                    {
                        exp.type = EnumExpressionType.METHOD;
                        return exp;
                    }

                    if (Modifiers.Contains(token.data))
                    {
                        exp.type = EnumExpressionType.MODIFIER;
                        exp.data = token.data;
                        return exp;
                    }

                    Token differ = nextToken();
                    if (differ.tokenType == EnumTokenType.BRACKET_OPEN)
                    {
                        rewindToken();

                        exp.type = EnumExpressionType.CALL;
                        exp.data = token.data;
                        return exp;
                    }
                    else
                    {
                        rewindToken();

                        exp.type = EnumExpressionType.STATIC_DATA;
                        exp.data = token.data;
                        return exp;
                    }
                    break;

                case EnumTokenType.SEMICOLON:
                    exp.type = EnumExpressionType.SEMICOLON;
                    return exp;
                    break;

                default:
                    exp.token = token;
                    exp.type = EnumExpressionType.END;
                    return exp;
            }

            return null;
        }

        private void rewindToken()
        {
            tokenPos--;
        }

        private void forwardToken()
        {
            tokenPos++;
        }

        private Token nextToken()
        {
            if (tokenPos  >= tokens.Count)
                return Token.END;

            return tokens[tokenPos++];
        }

        private Expression previewExpression()
        {
            int token = tokenPos;
            Expression exp = parseNext();
            tokenPos = token;
            return exp;
        }

        private Token previewNext()
        {
            Token tok = nextToken();
            rewindToken();
            return tok;
        }

        private bool IsNumber(string data)
        {
            int integer;
            return (int.TryParse(data, out integer));
            
        }

        private Expression requireNext(EnumExpressionType eType)
        {
            Expression exp = parseNext();
            if (exp.type == eType)
                return exp;
            else
            {
                Console.WriteLine("Expected " + eType.ToString() + ", found "+ exp.type.ToString());
                Environment.Exit(-1);
                return null;
            }
        }
    }

}
